package com.aabte.lota.auth.auth.token;

import com.aabte.commons.encrypt.rsa.RSAPrivateKeyEncryptor;
import com.aabte.commons.encrypt.rsa.RSAPublicKeyEncryptor;
import com.aabte.commons.json.JSONUtils;
import com.aabte.lota.auth.auth.token.exception.TokenException;
import com.aabte.lota.auth.auth.token.exception.TokenExpiredException;
import com.aabte.lota.auth.auth.token.exception.TokenParseException;
import com.aabte.lota.auth.auth.token.payload.TokenPayloadDTO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.DefaultClaims;
import java.time.Instant;
import java.util.Date;
import javax.annotation.Resource;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/4/5
 */
@Data
@Component
public class JWTTokenUtils {

  private static final JWTBase64Decoder DECODER = JWTBase64Decoder.getInstance();
  private static final JWTBase64Encoder ENCODER = JWTBase64Encoder.getInstance();
  public static final String USER = "user";

  @Value("${spring.application.name}")
  private String applicationName;

  @Resource private JWTProperites jwtProperites;

  @Resource private RSAPrivateKeyEncryptor rsaPrivateKeyEncryptor;
  @Resource private RSAPublicKeyEncryptor rsaPublicKeyEncryptor;

  public Claims createClaims(TokenPayloadDTO tokenPayload) {
    return createClaims(USER, tokenPayload);
  }

  public Claims createClaims(String key, TokenPayloadDTO tokenPayload) {
    long expirationTimeMillis = generateExpirationTimeMillis();
    Date expiration = new Date(expirationTimeMillis);

    Claims claims = new DefaultClaims();
    claims.setIssuer(applicationName);
    claims.setSubject(tokenPayload.getUsername());
    claims.put(key, tokenPayload);
    claims.setAudience(tokenPayload.getUsername());
    claims.setExpiration(expiration);
    claims.setIssuedAt(new Date());
    claims.setId(tokenPayload.getUid());
    return claims;
  }

  public String generateToken(Claims claims) {
    return Jwts.builder()
        .setClaims(claims)
        .setExpiration(claims.getExpiration())
        .signWith(rsaPrivateKeyEncryptor.key())
        .base64UrlEncodeWith(ENCODER)
        .compact();
  }

  public long parseTokenIssuedAt(String token) throws TokenException {
    Claims claims = parseToken2Claims(token);
    Date issuedAt = claims.getIssuedAt();
    return issuedAt.getTime();
  }

  public Jws<Claims> parseToken2Jws(String token) throws TokenException {
    try {
      return Jwts.parserBuilder()
          .setSigningKey(rsaPublicKeyEncryptor.key())
          .base64UrlDecodeWith(DECODER)
          .build()
          .parseClaimsJws(token);
    } catch (ExpiredJwtException e) {
      throw new TokenExpiredException("Token expired", e);
    } catch (Exception e) {
      throw new TokenParseException("Token incorrect", e);
    }
  }

  public Claims parseToken2Claims(String token) throws TokenException {
    Jws<Claims> claimsJws = parseToken2Jws(token);
    return claimsJws.getBody();
  }

  public TokenPayloadDTO parseToken(String token) throws TokenException {
    Claims body = parseToken2Claims(token);
    return JSONUtils.fromJson(JSONUtils.toJson(body.get(USER)), TokenPayloadDTO.class);
  }

  /** 生成token时间 = 当前时间 + expiration（properties中配置的失效时间） */
  private long generateExpirationTimeMillis() {
    return Instant.now().toEpochMilli() + jwtProperites.getExpiration() * 1000;
  }
}
